package com.annchain.og_browser.service.impl;

import com.annchain.og_browser.entity.*;
import com.annchain.og_browser.mapper.*;
import com.annchain.og_browser.service.KnownAddressService;
import com.annchain.og_browser.util.ByteUtils;
import com.annchain.og_browser.vo.PerformanceVO;
import com.annchain.og_browser.service.OgbBlockService;
import com.annchain.og_browser.util.TimeUtil;
import com.annchain.og_browser.vo.BasePageResponse;
import com.annchain.og_browser.vo.LatestBlockVO;
import com.annchain.og_browser.vo.OgbBlockVO;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;


@Service
public class OgbBlockServiceImpl implements OgbBlockService {
    private static final Logger logger = LoggerFactory.getLogger(OgbBlockServiceImpl.class);
    @Autowired
    private OgbBlockMapper ogbBlockMapper;
    @Autowired
    private OgbKnowAddressMapper ogbKnowAddressMapper;
    @Autowired
    private OgbTxRelationMapper ogbTxRelationMapper;
    @Autowired
    private OgbAddressMapper ogbAddressMapper;
    @Autowired
    private OgbProfileHistoryMapper ogbProfileHistoryMapper;
    @Autowired
    private KnownAddressService knownAddressService;


    @Override
    @ApiOperation("区块列表")
    public BasePageResponse<List<LatestBlockVO>> findData(Integer page, Integer limit) {
        BasePageResponse pageResponse = new BasePageResponse();
        IPage<OgbBlock> ogbBlockIPage = new Page<>(page, limit);
        ((Page<OgbBlock>) ogbBlockIPage).setOptimizeCountSql(true);
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.orderByDesc("height");
        IPage<OgbBlock> result = ogbBlockMapper.selectPage(ogbBlockIPage, wrapper);
        if (result.getRecords().size() == 0) {
            logger.info("无数据");
            return null;
        }
        pageResponse.setTotal(result.getTotal());
        pageResponse.setPageNum(result.getCurrent());
        pageResponse.setPageSize(result.getSize());
        List<OgbBlock> ogbBlockList = result.getRecords();
        List<LatestBlockVO> latestBlockVOList = new ArrayList<>();
        for (OgbBlock ogbBlock : ogbBlockList) {
            LatestBlockVO latestBlockVO = new LatestBlockVO();
            BeanUtils.copyProperties(ogbBlock, latestBlockVO);
            latestBlockVO.setMiner(knownAddressService.getAddress(ogbBlock.getMiner()));

            latestBlockVOList.add(latestBlockVO);
            //利用工具类计算时间差

            if (ogbBlock.getOutputTime() != null) {
                Long currTime = new Date().getTime();
                Long outputTime = ogbBlock.getOutputTime().getTime();

                latestBlockVO.setOutputTime(outputTime);
                latestBlockVO.setTimeDiff(currTime - outputTime);
            }

        }
        pageResponse.setData(latestBlockVOList);
        return pageResponse;
    }

    @Override
    @ApiOperation("通过哈希值查找区块")
    public OgbBlockVO findByHash(String hash) {
        QueryWrapper wrapper = new QueryWrapper();
        OgbBlockVO ogbBlockVO = new OgbBlockVO();
        wrapper.eq("hash", hash);
        OgbBlock ogbBlock = ogbBlockMapper.selectOne(wrapper);
        if (ogbBlock == null) {
            logger.info("该区块不存在数据库中");
            return null;
        }
        BeanUtils.copyProperties(ogbBlock, ogbBlockVO);
        TimeUtil timeUtil = new TimeUtil();
        ogbBlockVO.setOutputTime(timeUtil.TimeStampToString(ogbBlock.getOutputTime()));
        //获取父节点列表
        List<OgbTxRelation> ogbTxRelationList = new ArrayList<>();
        wrapper = new QueryWrapper();
        wrapper.eq("hash", ogbBlock.getHash());
        ogbTxRelationList = ogbTxRelationMapper.selectList(wrapper);
        List<String> parentList = new ArrayList<>();
        if (ogbTxRelationList.size() != 0) {
            for (OgbTxRelation txRelation : ogbTxRelationList) {
                parentList.add(txRelation.getParentHash());
            }
        } else {
            parentList = null;
            logger.info("无父节点列表");
        }
        ogbBlockVO.setParentHash(parentList);
        ogbBlockVO.setType("序列");

        ogbBlockVO.setMiner(knownAddressService.getAddress(ogbBlock.getMiner()));

        return ogbBlockVO;
    }

    @Override
    @ApiOperation("通过高度查找区块")
    public OgbBlockVO findByHeight(Integer height) {
        QueryWrapper wrapper = new QueryWrapper();
        OgbBlockVO ogbBlockVO = new OgbBlockVO();
        //通过高度查区块
        wrapper.eq("height", height);
        OgbBlock ogbBlock = ogbBlockMapper.selectOne(wrapper);
        if (null == ogbBlock) {
            logger.info("该区块不存在数据库中");
            return null;
        }
        BeanUtils.copyProperties(ogbBlock, ogbBlockVO);
        TimeUtil timeUtil = new TimeUtil();
        ogbBlockVO.setOutputTime(timeUtil.TimeStampToString(ogbBlock.getOutputTime()));

        List<OgbTxRelation> ogbTxRelationList = new ArrayList<>();
        //通过哈希值查父节点列表
        wrapper = new QueryWrapper();
        wrapper.eq("hash", ogbBlock.getHash());
        ogbTxRelationList = ogbTxRelationMapper.selectList(wrapper);
        List<String> parentList = new ArrayList<>();
        if (ogbTxRelationList.size() != 0) {
            for (OgbTxRelation txRelation : ogbTxRelationList) {
                parentList.add(txRelation.getParentHash());
            }
        } else {
            logger.info("无父节点列表");
            parentList = null;
        }
        ogbBlockVO.setParentHash(parentList);
        ogbBlockVO.setType("序列");

        ogbBlockVO.setMiner(knownAddressService.getAddress(ogbBlock.getMiner()));

        return ogbBlockVO;
    }

    @Override
    //插入已知地址表数据
    public void addKonwnAddress(String address, String name, String url) {
        OgbKnownAddress ogbKnownAddress = new OgbKnownAddress();
        ogbKnownAddress.setName(name);
        ogbKnownAddress.setAddress(address);
        ogbKnownAddress.setLogoUrl(url);
        ogbKnowAddressMapper.insert(ogbKnownAddress);
        logger.info("数据已经插入已知地址表");
    }


    @Override
    /**
     * 性能表数据提取展示
     */
    public PerformanceVO findAll() {
        //区块按高度降序排列

        System.out.println(new Date());
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.orderByDesc("height");
        wrapper.last("limit 0,101");
        List<OgbBlock> ogbBlockList = ogbBlockMapper.selectList(wrapper);

        System.out.println(new Date());
        OgbBlock ogbBlock = new OgbBlock();
        PerformanceVO performanceVO = new PerformanceVO();
        //最新区块
        if (ogbBlockList.size() != 0) {
            ogbBlock = ogbBlockList.get(0);
        }
        performanceVO.setLatestBlock(ogbBlock.getHeight());

        //全网账户数
        wrapper = new QueryWrapper();
        wrapper.isNotNull("address");
        Integer accountNum = ogbAddressMapper.selectCount(wrapper);
        performanceVO.setAccountNum(accountNum);

        //求TPS
        long outputTimeAll = 0;
        int txCountAll = 0;
        int timer = 0;

        //出块时间表
        List<Long> outputTimeList = new ArrayList<>();
        //TPS表
        List<Integer> tpsList = new ArrayList<>();
        //区块交易量表
        List<Integer> txCountList = new ArrayList<>();
        int len = ogbBlockList.size();
        for (int i = 0; i < len - 1; i++) {
            OgbBlock nowOgbBlock = ogbBlockList.get(i);
            OgbBlock preOgbBlock = ogbBlockList.get(i + 1);
            long outputTime = 0;
            if (preOgbBlock.getOutputTime() != null && nowOgbBlock.getOutputTime() != null) {
                outputTime = TimeUtil.OutputTimeDiff(preOgbBlock.getOutputTime(), nowOgbBlock.getOutputTime());
                //logger.info("####### outputTime: {}", outputTime);
            }

            if (i == 0) {
                performanceVO.setPreOutputTime(outputTime);
            }

            int tps = 0;
            if (nowOgbBlock.getTransactionCount() != null && outputTime != 0) {
                tps = (int) (nowOgbBlock.getTransactionCount() / outputTime);
                //logger.info("####### tps: {}",tps);
            }


            if (timer < 20) {
                outputTimeList.add(outputTime);
                //logger.info("####### outputTimeList: {}", outputTimeList);
                tpsList.add(tps);
                txCountList.add(nowOgbBlock.getTransactionCount());
            }


            if (timer == 99) {
                //最近100个块出块时间
                performanceVO.setAvgTime(outputTimeAll / 100);
                break;
            }
            timer++;

            txCountAll += nowOgbBlock.getTransactionCount();
            outputTimeAll += outputTime;

        }
        System.out.println(new Date());
        //logger.info("####### txCountAll: {}", txCountAll);
        //logger.info("####### outputTimeAll: {}", outputTimeAll);
        performanceVO.setOutputTimeGraph(outputTimeList);
        performanceVO.setTpsGraph(tpsList);
        performanceVO.setBlockTxNum(txCountList);
        performanceVO.setAllTPS((int) (txCountAll / outputTimeAll));

        //账本大小、资产类型数量和节点数

        wrapper = new QueryWrapper();
        wrapper.orderByDesc("id");
        wrapper.last("limit 0,9");

        List<OgbProfileHistory> list = ogbProfileHistoryMapper.selectList(wrapper);
        for (int i = 0; i <= 8; i++) {
            switch (list.get(i).getField()) {
                case "tps_max":
                    performanceVO.setMaxTPS(list.get(i).getValueDouble());
                    break;
                case "nodes":
                    performanceVO.setNodeNum(list.get(i).getValueInt());
                    break;
                case "ledger_size":
                    performanceVO.setLedgerSize(ByteUtils.getNetFileSizeDescription(list.get(i).getValueLong()));
                    break;
                case "property_size":
                    performanceVO.setPropertyTypeNum(list.get(i).getValueInt());
                    break;
            }
        }
        System.out.println(new Date());
        return performanceVO;
    }


}
